let charTypes = require('./char-type');
let tokenTypes = require('../common/token-types');
let stateTypes = require('./state-types');

let lexerAnalyze = {
  inputs: [],
  index: 0,
  char: '',
  chunkStart: 0,
  strChunk: '',
  state: stateTypes.NONE,
  expStart: '',
  expEnd: '',
  expDeep: 0,
  result: [],
  analyze(str) {
    this.inputs = str.trim().split('');
    do {
      this.getChar();
      if (typeof this.char == 'undefined') {
        // 结束
        if (this.strChunk) {
          this.result.push(this.genToken());
        }
      } else {
        // 过滤空状态下的空白字符
        if (this.getCharType(this.char) == charTypes.blank) {
          if (this.state == stateTypes.NONE) {
            continue;
          }
        }
        if (this.shouldStateChange()) {
          let nextState = this.getNextState();
          this.stateWillChange(nextState);
          this.setState(nextState);
          if (nextState == stateTypes.NONE) {
            this.expStart = this.expEnd = '';
          }
        } else {
          this.addToChunk();
          if (this.state == stateTypes.EXP) {
            if (this.getCharType(this.char) == charTypes.exp_div) {
              if (this.char == this.expStart) {
                this.expDeep++;
              } else if (this.char == this.expEnd) {
                this.expDeep--;
              }
            }
          }
        }
      }
    } while (this.index < this.inputs.length + 1);
    return this.result;
  },
  getCharType(char) {
    if (/[a-zA-Z]/.test(char)) {
      return charTypes.letter;
    } else if (/\d/.test(char)) {
      return charTypes.number;
    } else if (/[\=]/.test(char)) {
      return charTypes.assign;
    } else if (/[>]/.test(char)) {
      return charTypes.operator_suffix;
    } else if (/\./.test(char)) {
      return charTypes.dot;
    } else if (/\$/.test(char)) {
      return charTypes.dollor;
    } else if (/[@#]/.test(char)) {
      return charTypes.pos;
    } else if (/\s/.test(char)) {
      return charTypes.blank;
    } else if (/["\(\)\{\}\[\]<>]/.test(char)) {
      return charTypes.exp_div;
    } else if (char == '/') {
      return charTypes.com_pre_div;
    } else if (char == '*') {
      return charTypes.com_div;
    } else if (char == ':') {
      return charTypes.uri;
    } else if (char == ';') {
      return charTypes.semi;
    } else if (char == '-') {
      return charTypes.point;
    }
  },
  getChar() {
    this.char = this.inputs[this.index];
    this.index++;
  },
  addToChunk() {
    this.strChunk += this.char;
  },
  getStateFromChar(char) {
    switch (this.getCharType(char)) {
      case charTypes.letter:
        return stateTypes.WORD;
      case charTypes.number:
        return stateTypes.DIGIT;
      case charTypes.point:
        return stateTypes.POINT;
      case charTypes.uri:
        return stateTypes.URI;
      case charTypes.assign:
        return stateTypes.ASSIGN;
      case charTypes.dot:
        return stateTypes.DOT;
      case charTypes.dollor:
        return stateTypes.DOLLOR;
      case charTypes.pos:
        return stateTypes.POS;
      case charTypes.semi:
        return stateTypes.SEMI;
      case charTypes.exp_div:
        return stateTypes.EXP;
      case charTypes.blank:
        return stateTypes.NONE;
    }
  },
  shouldStateChange() {
    let charType = this.getCharType(this.char);
    if (this.state == stateTypes.NONE) {
      return charType !== charTypes.blank;
    } else {
      switch (this.state) {
        case stateTypes.EXP:
          if (this.expStart) {
            switch (charType) {
              case charTypes.exp_div:
                return this.char == this.expEnd && this.expDeep == 0;
            }
          } else {
            switch (charType) {
              case charTypes.pos:
              case charTypes.blank:
              case charTypes.semi:
                return true;
              default:
                return false;
            }
          }
        case stateTypes.COM_ADV_START:
          return charType == charTypes.com_div;
        case stateTypes.COM:
          return charType == charTypes.com_div;
        case stateTypes.COM_ADV_END:
          return charType == charTypes.com_pre_div;
        case stateTypes.URI:
          return [charTypes.pos, charTypes.blank].includes(charType);
        case stateTypes.ASSIGN:
        case stateTypes.POINT:
          return this.getCharType(this.char) != charTypes.operator_suffix;
        default:
          return this.getStateFromChar(this.char) != this.state;
      }
    }
  },
  stateWillChange(nextState) {
    switch (nextState) {
      // 表达式开始
      case stateTypes.EXP:
        if (this.state != stateTypes.NONE) {
          this.result.push(this.genToken());
          this.addToChunk();
        } else {
          this.addToChunk();
        }
        if (this.getCharType(this.char) == charTypes.exp_div) {
          this.expStart = this.char;
          this.expEnd = this.getMatchedExpEnd();
        }
        break;
      case stateTypes.COM_END:
        if (this.state != stateTypes.NONE) {
          this.result.push(this.genToken());
        }
        break;
      case stateTypes.COM:
        this.addToChunk();
        break;
      // 注释分界字符不加入chunk
      case stateTypes.COM_ADV_START:
      case stateTypes.COM_START:
      case stateTypes.COM_ADV_END:
        if (this.state != stateTypes.NONE) {
          this.result.push(this.genToken());
        }
        break;
      case stateTypes.URI:
        this.addToChunk();
        break;
      case stateTypes.NONE:
        if (this.expEnd) {
          if (this.expEnd == ' ') {
            this.result.push(this.genToken());
          } else {
            this.addToChunk();
            this.result.push(this.genToken());
          }
        } else {
          if (this.state != stateTypes.NONE) {
            this.result.push(this.genToken());
          }
        }
        break;
      default:
        if (this.state != stateTypes.NONE) {
          this.result.push(this.genToken());
        }
        this.addToChunk();
        break;
    }
    // 新chunk开始时设置chunk开始位置
    if (![stateTypes.NONE, stateTypes.URI].includes(nextState)) {
      this.chunkStart = this.index - 1;
    }
  },
  getNextState() {
    let charType = this.getCharType(this.char);
    switch (this.state) {
      // 运算符状态
      case stateTypes.ASSIGN:
        if (charType == charTypes.exp_div) {
          return stateTypes.EXP;
        } else if (charType == charTypes.com_pre_div) {
          // 接 / 号 > 注释预开始状态
          return stateTypes.COM_ADV_START;
        } else if (charType == charTypes.blank) {
          // 接空白字符 > 空状态
          return stateTypes.NONE;
        } else {
          // 接其他字符 > 表达式状态
          return stateTypes.EXP;
        }
      // 表达式开始状态
      case stateTypes.EXP:
        // 接配对结束字符 > 空状态
        if (this.expStart) {
          if (this.char == this.expEnd) {
            return stateTypes.NONE;
          }
        } else {
          switch (charType) {
            case charTypes.pos:
            case charTypes.uri:
            case charTypes.blank:
            case charTypes.semi:
              return this.getStateFromChar(this.char);
          }
        }
      // 空状态
      case stateTypes.NONE:
        if (this.result.length == 0) {
          return this.getStateFromChar(this.char);
        } else {
          let lastToken = this.result[this.result.length - 1];
          if (lastToken.type == tokenTypes.ASSIGN) {
            return stateTypes.EXP;
          } else {
            return this.getStateFromChar(this.char);
          }
        }
      default:
        if (charType == charTypes.blank) {
          return stateTypes.NONE;
        } else {
          // 返回与字符相同的状态
          return this.getStateFromChar(this.char);
        }
    }
  },
  getMatchedExpEnd() {
    switch (this.char) {
      case '"':
        return '"';
      case '(':
        return ')';
      case '[':
        return ']';
      case '{':
        return '}';
      case '<':
        return '>';
      default:
        return ' ';
    }
  },
  setState(state) {
    this.state = state;
  },
  genToken() {
    let start = this.chunkStart;
    let end = this.index - 1;
    let type = '';
    switch (this.state) {
      case stateTypes.WORD:
        type = tokenTypes.WORD;
        break;
      case stateTypes.DIGIT:
        type = tokenTypes.DIGIT;
        break;
      case stateTypes.DOT:
        type = tokenTypes.DOT;
        break;
      case stateTypes.DOLLOR:
        type = tokenTypes.DOLLOR;
        break;
      case stateTypes.ASSIGN:
        type = tokenTypes.ASSIGN;
        break;
      case stateTypes.POINT:
        type = tokenTypes.POINT;
        break;
      case stateTypes.POS:
        type = tokenTypes.POS;
        break;
      case stateTypes.URI:
        type = tokenTypes.URI;
        break;
      case stateTypes.SEMI:
        type = tokenTypes.SEMI;
        break;
      case stateTypes.EXP:
        type = tokenTypes.EXPRESSION;
        break;
      case stateTypes.COM_ADV_END:
        type = tokenTypes.COMMENT;
        break;
    }
    let token = {
      type,
      value: this.strChunk,
      start,
      end
    };
    this.strChunk = '';
    return token;
  }
};

module.exports = lexerAnalyze;
